package com.xnyzc.lhy.gateway.config;

import com.alibaba.fastjson.JSON;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.xnyzc.lhy.common.component.service.IGatewayService;
import com.xnyzc.lhy.common.constant.CommonConstant;
import com.xnyzc.lhy.common.entity.OaGatewayIgnore;
import com.xnyzc.lhy.common.entity.PangClient;
import com.xnyzc.lhy.common.entity.Rv;
import com.xnyzc.lhy.common.entity.Sb;
import com.xnyzc.lhy.common.exception.PangException;
import com.xnyzc.lhy.common.penum.EErrorCode;
import com.xnyzc.lhy.common.util.AesUtil;
import com.xnyzc.lhy.common.util.CheckUtil;
import com.xnyzc.lhy.common.util.HttpUtil;
import com.xnyzc.lhy.common.util.MD5Util;
import com.xnyzc.lhy.common.util.sign.BodyReaderHttpServletRequestWrapper;
import com.xnyzc.lhy.common.util.sign.RequestHeaderSign;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.List;
import java.util.Objects;

import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_DECORATION_FILTER_ORDER;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;

/**
 * @Description:
 * @Author: scootXin
 * @Date: 2019/9/26 20:49
 */

@Slf4j
@Component
public class SignFilter extends ZuulFilter {

    @Value("${global.pang.signCheck}")
    private boolean signCheck;
    @Autowired
    private IGatewayService gatewayService;

    /**
     * filterType方法: filter的类型，有”pre”, “route”, “post”, “error”, “static”
     *
     * @return
     */
    @Override
    public String filterType() {
        return PRE_TYPE;
    }

    /**
     * filterOrder方法：优先级，级别越高，越快被执行
     *
     * @return
     */
    @Override
    public int filterOrder() {
        return PRE_DECORATION_FILTER_ORDER - 1;
    }

    /**
     * shouldFilter方法：开关，如果是true，run方法会执行，否则不会执行
     *
     * @return
     */
    @Override
    public boolean shouldFilter() {

        if (!signCheck) {
            return false;
        }

        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest req = ctx.getRequest();

        String contentType = req.getHeader("Content-Type");
        // 忽略application/x-www-form-urlencoded,multipart/form-data请求
        boolean contentTypeBoolean = contentType != null && (contentType.startsWith(CommonConstant.LOGISTICS_HEADER_CONTENT_TYPE_FORM) || contentType.startsWith(CommonConstant.LOGISTICS_HEADER_CONTENT_TYPE));
        if (contentTypeBoolean) {
            return false;
        }

        // 忽略非get或post请求
        String method = req.getMethod();
        if (!CommonConstant.REQUEST_POST.equals(method.toLowerCase()) && !CommonConstant.REQUEST_GET.equals(method.toLowerCase())) {
            return false;
        }

        /**
         * 处理忽略列表策略
         */
        AntPathMatcher antPathMatcher = new AntPathMatcher();

        /**
         * 忽略请求header和验签
         */
        List<OaGatewayIgnore> oaGatewayIgnoreList = gatewayService.getSignIgnoreList();

        /**
         * 在忽略列表中则自动过滤
         */
        String realUrl = req.getServletPath();
        if (CheckUtil.collectionIsNotEmpty(oaGatewayIgnoreList)) {
            for (OaGatewayIgnore oaGatewayIgnore : oaGatewayIgnoreList) {
                if (antPathMatcher.match(oaGatewayIgnore.getIgnoreUrl(), realUrl)) {
                    return false;
                }
            }
        }

        // 忽略之前发布的版本
        String appCode = req.getHeader("appCode");
        if (CheckUtil.strIsNotEmpty(appCode)) {
            String[] appCodeArray = appCode.split(":");
            if (Objects.equals(SignConstant.ANDROID, appCodeArray[0]) && appCodeArray[1].compareTo(SignConstant.ANDROID_APP_CODE) <= 0) {
                return false;
            }
        }

        return true;
    }

    /**
     * run方法：filter执行的逻辑操作
     *
     * @return
     * @throws ZuulException
     */
    @Override
    public Object run() throws ZuulException {

        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();

        try {
            ctx.setRequest(new BodyReaderHttpServletRequestWrapper(request));
        } catch (IOException e) {
            log.warn("-- error when wrap request --");
            PangException.create(EErrorCode.signError);
        }

        RequestHeaderSign requestHeader = RequestHeaderSign.create(request);

        // 验证tokenHeader
        if (!checkTokenHeader(requestHeader)) {
            throw PangException.create(EErrorCode.signError, "缺少验签参数");
        }

        // 先验证时间
        checkTime(requestHeader.getReqTime());

        // 获取clientId信息
        PangClient clientSecret = gatewayService.getPangClient(requestHeader.getClientId());

        if (clientSecret == null) {
            log.warn("-- clientId不存在 --");
            throw PangException.create(EErrorCode.signError, "验签失败");
        }

        String method = request.getMethod();
        String originSign = "";
        // 获取待验证串
        if (CommonConstant.REQUEST_POST.equals(method.toLowerCase())) {
            originSign = getSignOriginPostStr(request, requestHeader);
        } else if (CommonConstant.REQUEST_GET.equals(method.toLowerCase())) {
            originSign = getSignOriginGetStr(request, requestHeader);
        } else {
            return null;
        }

        // 验证sign
        String before = Sb.create().add(originSign).add("&").add(requestHeader.getClientId()).add("&").add(requestHeader.getReqTime()).toString();
        String after = AesUtil.encrypt(before, clientSecret.getClientSecurity());
        String afterSign = MD5Util.MD5(after);
        if (!requestHeader.getSign().equalsIgnoreCase(afterSign)) {
            log.warn("加密串：{},AES加密串：{},加密后MD5:{}", before, after, afterSign);
            throw PangException.create(EErrorCode.signError, "验签失败");
        }
        return null;
    }

    /**
     * 从Post请求中获取待加密串
     *
     * @return
     */
    public String getSignOriginPostStr(HttpServletRequest request, RequestHeaderSign requestHeader) {
        String contentType = request.getHeader("Content-Type");
        String data = null;

        // 文件流或富文本以时间戳为加密串
        if (contentType != null && contentType.startsWith(CommonConstant.LOGISTICS_HEADER_CONTENT_TYPE)) {
            data = requestHeader.getReqTime();
        } else {
            data = HttpUtil.getRequestPayload(request);
        }

        // 处理流为空情况
        if (CheckUtil.strIsEmpty(data)) {
            data = "undefined";
        }

        return data;
    }

    /**
     * 从Get请求中获取待加密串
     *
     * @return
     */
    public String getSignOriginGetStr(HttpServletRequest request, RequestHeaderSign requestHeader) {
        String contentType = request.getHeader("Content-Type");
        String data = null;

        // 文件流或富文本以时间戳为加密串
        if (contentType != null && contentType.startsWith(CommonConstant.LOGISTICS_HEADER_CONTENT_TYPE)) {
            data = requestHeader.getReqTime();
        } else {
            data = HttpUtil.getRequestParams(request);
        }

        // 处理流为空情况
        if (CheckUtil.strIsEmpty(data)) {
            data = "undefined";
        }

        return data;
    }

    /**
     * 写错误反馈
     *
     * @param ctx
     * @param code
     * @throws IOException
     */
    public void writeResponse(RequestContext ctx, EErrorCode code) throws IOException {
        ctx.setSendZuulResponse(false);
        ctx.getResponse().setContentType("text/html;charset=utf-8");
        ctx.getResponse().getWriter().write(JSON.toJSONString(Rv.wrap(code, CheckUtil.strIsEmpty(code.getDesc()) ? "验签失败" : code.getDesc(), null)));
    }

    /**
     * 进行请求时间验签
     *
     * @return
     */
    public boolean checkTime(String time) {

        //token构建时间
        DateTime tokenTime = new DateTime(Long.parseLong(time));
        tokenTime = tokenTime.plusSeconds(CommonConstant.CLINET_EXPIRE_TIME);

        if (tokenTime.isBeforeNow()) {
            log.warn("timeSign too early");
            throw PangException.create(EErrorCode.signError, "验签失败");
        }

        return true;
    }

    /**
     * 验证tokenHeader
     *
     * @param header
     * @return
     */
    public boolean checkTokenHeader(RequestHeaderSign header) {

        return CheckUtil.strIsNotEmpty(header.getAppCode(), header.getClientId(), header.getSign(), header.getReqTime(), header.getUserType(), header.getTag());
    }
}
